package fr.vod.threads;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.view.View;
import fr.vod.gauss.File.File;
import fr.vod.gauss.File.FileImpl;
import fr.vod.gauss.encoder.Encoder;
import fr.vod.gauss.encoder.EncoderImp;
import fr.vod.gauss.matrice.Matrice;
import fr.vod.gauss.matrice.MatriceImpl;
import fr.vod.gauss.matrice.PivotDeGauss;
import fr.vod.gauss.matrice.PivotDeGaussImpl;
import fr.vod.gauss.net.Format;
import fr.vod.gauss.net.FormatImpl;
import fr.vod.gauss.operations.CorpsDeGalloisOperation;
import fr.vod.ihm.adapter.ExpandableListAdapter;
import fr.vod.ihm.videoshareucp.MainActivity;
import fr.vod.net.ReceiverMulticast;
import fr.vod.net.SenderMulticast;
import fr.vod.util.ExchangeProperties;
import fr.vod.util.LogHelper;
import fr.vod.videoshareucp.R;
import fr.vod.videoshareucp.R.layout;

public class TransferTask extends AsyncTask<String, String, String> {

	// / tag des logs
	private final static String logTag = TransferTask.class.getName();
	private final int BLOCK = 1024;
	private final int PACKET = 1024;
	private int BLOCK_PIVAUT_DE_GAUSS;

	private MainActivity activity;
	int numberInterlocutor = 0;

	// / flag indiquant si le service est binde avec l'activite
	boolean flagBind = false;

	// / Instance "attrapant" les messages venant du service
	Messenger messenger = new Messenger(new IncomingHandler(this));
	Intent serviceIntent;

	// UI Elements
	HashMap<String, Boolean> availableDevices;
	private ExpandableListAdapter listAdapter;

	// Transfert Elements
	private ReceiverMulticast receiver;
	private SenderMulticast sender;
	private SenderMulticast sData;
	private ReceiverMulticast rData;

	// Variables
	private boolean isServer;
	private boolean isTransfering = false;
	private Integer fileSize = 0;
	private String videoName;

	private static final String FILE_LENGTH = "FILE_LENGTH_";
	private static final String LENGTH_OK = "LENGTH_OK";
	private static final String BLOCK_NUMBER = "BLOCK_NUMBER_";
	private static final String EOF = "END_OF_FILE";

	private CommandThread ct;
	private boolean displayHeartbeatLog;

	public TransferTask(MainActivity activity,
			ExpandableListAdapter listAdapter, boolean isServer,
			String pathVideo, boolean displayHeartbeatLog, int blocSize, int packetSize, int subBlocSize) {
		LogHelper.entree(logTag, "constructor");
		this.activity = activity;
		this.listAdapter = listAdapter;
		this.isServer = isServer;
		this.videoName = pathVideo;
		this.displayHeartbeatLog = displayHeartbeatLog;
		//this.BLOCK = blocSize;
		//this.PACKET = packetSize;
		//this.BLOCK_PIVAUT_DE_GAUSS = subBlocSize;
	}

	@Override
	protected void onPreExecute() {
		LogHelper.entree(logTag, "onPreExecute");
		listAdapter.setIsServer(isServer);

	};

	@Override
	protected String doInBackground(String... params) {
		initHeartbeatService();
		LogHelper.entree(logTag, "doInBackground");
		flagBind = true;

		SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
		Date date = new Date();
		listAdapter.updateInitTimestamp(dateFormat.format(date));

		publishProgress("\tHeartbeat Started.", "heartbeat log");

		publishProgress("\t** Initializing connection **");

		// Initialisation
		if (isServer) {
			java.io.File file = new java.io.File(Environment
					.getExternalStorageDirectory().getPath()
					+ "/Movies/"
					+ videoName);

			listAdapter.updateFileSize(new DecimalFormat("##.##")
					.format((double) file.length() / (double) (1024 * 1024))
					+ " Mo");
			fileSize = (int) file.length();
			int defaultHeartbeatRefresh = ExchangeProperties.HEARTBEAT_REFRESH;
			ExchangeProperties.HEARTBEAT_REFRESH = 5;
			waitForDevices();
			ExchangeProperties.HEARTBEAT_REFRESH = defaultHeartbeatRefresh;

			isTransfering = initServer(file);
		} else {
			listAdapter.updateProgressBar("0");
			isTransfering = initClient();
			if (isTransfering)
				listAdapter.updateFileSize(new DecimalFormat("##.##")
						.format((double) fileSize / (double) (1024 * 1024))
						+ " Mo");
		}

		if (isTransfering) {

			publishProgress("\t** Initialization completed **\n");
			publishProgress("Starting transfer");
		} else {
			publishProgress("\t** Initialization failed **\n");
			return null;
		}

		if (isServer) {
			try {
				sData.startWatching();
				FileInputStream fis = new FileInputStream(Environment
						.getExternalStorageDirectory().getPath()
						+ "/Movies/"
						+ videoName);
				Encoder en = new EncoderImp(new CorpsDeGalloisOperation());
				int end;
				// / taille de block et selection Ã recuperer dans le fichier de
				// ressources
				File pi = new FileImpl(fis, en, BLOCK, PACKET, BLOCK_PIVAUT_DE_GAUSS);
				// / tanque non fin de fichier et il y a des clients dispo sur
				// le reseau
				DataSender ds = new DataSender(pi, sData);
				ds.read();

				
				/** verifie si il y a un tranfert en cours **/
				long block = verifierTransfertEncours(BLOCK_NUMBER);
				
				/** verifie que tous les clients demandent le même block **/
				if(block < 0){
					checkCommandClient(BLOCK_NUMBER + pi.getBlockNumber());
				
				}
				else if(block > 0)
				{
					for(int i=0;i < block + 25;i++){
						pi.readNextBlock();
					}
					checkCommandClient(BLOCK_NUMBER + pi.getBlockNumber());
				}
				else
				{
					checkCommandClient(BLOCK_NUMBER + pi.getBlockNumber());
				}
				
				ds.start();
				LogHelper.information(logTag, "Datasender started");
				do {
					if (pi.getBlockNumber() % 50 == 0)
						publishProgress("Tranfert block " + pi.getBlockNumber());

					//publishProgress("Sending block " + pi.getBlockNumber());

					/** verifie que tous les clients demandent le même block **/
					checkCommandClient(BLOCK_NUMBER + (pi.getBlockNumber() + 1));

					end = ds.read();

					int progress = (int) (((double) pi.getBlockNumber() / ((double) fileSize / BLOCK)) * 100);
					
					updateProgressAdapterInformations(Integer.toString(progress));
					updateUploadAdapterInformations(Double.toString(sData.getUploadSpeed()));
					if (pi.getBlockNumber() % 50 == 0) {
						activity.runOnUiThread(new Runnable() {
							@Override
							public void run() {
								listAdapter.notifyDataSetChanged();
							}
						});
					}

				} while (end != -1 && flagBind);

				ds.end();
				for(int i=0;i<30;i++){
					sender.send(EOF);
				}
			} catch (Exception e) {
				// LogHelper.information("Error data tranfert server",
				// e.getMessage());
				// publishProgress("Error : " + e.getMessage());
			}

		} else {
			rData.startWatching();
			String totoPath = Environment.getExternalStorageDirectory()
					.getPath() + "/Movies/toto.mp4";
			java.io.File file = new java.io.File(totoPath);

			try {
				FileOutputStream fos = new FileOutputStream(file, false);
				long block = 0;
				byte[] data;
				PivotDeGauss pdg = new PivotDeGaussImpl(
						new CorpsDeGalloisOperation());

				int cptDisplay = 0;
				while(block < Math.ceil((double)fileSize / BLOCK)) {

					cptDisplay++;

					/** essaye de reconstruit le block **/
					data = rebuildBlock(pdg, BLOCK_NUMBER + block, block);

					/** ecriture du données dans le fichier **/
					if(data != null){
						fos.write(data);
					}
					/** passe au block suivant **/
					block++;
					
					int progress = (int) (((double) block / ((double) fileSize / BLOCK)) * 100);

					if (block % 300 == 0) {
						publishProgress("=> Total : " + fileSize
								/ BLOCK + " \tCurrent : " + block + " ("+progress+"%)");
						cptDisplay = 0;
					}
					
					
					
					updateProgressAdapterInformations(Integer.toString(progress));
					updateDownloadAdapterInformations(/*new DecimalFormat("##.##").format*/(Double.toString(rData.getDownloadSpeed())));
					if (block % 50 == 0) {
					activity.runOnUiThread(new Runnable() {
						@Override
						public void run() {
							updateEtaAdapterInformations(Integer.toString((int) (fileSize/1024 / rData.getDownloadSpeed())));
							listAdapter.notifyDataSetChanged();
						}
					});
					}
					
					data = null;
					
				}
				fos.close();

				publishProgress("UPDATE_PATH", totoPath);
				publishProgress("SUFFICIENT_VIDEO", totoPath);	
			} catch (Exception e) {
				LogHelper.information("Error Error data tranfert client",
						e.getMessage());
			}
		}
			
		// while (flagBind && isTransfering) {
		// Toast.makeText(activity.getBaseContext(), "transfering",
		// Toast.LENGTH_SHORT);

		// }

		return null;
	}

	private void updateEtaAdapterInformations(String eta) {
		listAdapter.updateEtaTime(eta);
	}

	private void updateDownloadAdapterInformations(String downloadSpeed) {
		listAdapter.updateDownloadSpeed(downloadSpeed);
	}
	
	private void updateUploadAdapterInformations(String uploadSpeed) {
		listAdapter.updateUploadSpeed(uploadSpeed);
	}

	private void updateProgressAdapterInformations(String progress) {
		listAdapter.updateProgressBar(progress);
	}
	
	

	private long verifierTransfertEncours(String cmd) {
		DatagramPacket dp;
		String ip, msg;
		List<String> list = new ArrayList<String>();
		long max = -1,tmp = -1;
		try {
			while (list.isEmpty() || !list.containsAll(availableDevices.keySet())) {
				/** **/
				dp = receiver.receivePacket();
				ip = dp.getAddress().getHostAddress();
				msg = new String(dp.getData()).trim();

				/** **/
				if (msg.startsWith(cmd) && !list.contains(ip)) {
					tmp = Long.valueOf(msg.substring(cmd.length()));
					max = max < tmp ? tmp : max;
					list.add(ip);
				}

				/** remove disconnected client **/
				for (String str : list) {
					if (!availableDevices.containsKey(str)) {
						list.remove(str);
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			LogHelper.erreur(logTag, "exception in the while");
		}
		return max;
	}

/*	private void appendToFile(FileOutputStream fos, byte[] data)
			throws IOException {
		for (int i = 0; i < data.length; i++)
			fos.write(data[i]);
		fos.write(data);
	}*/

	private void initHeartbeatService() {
		Intent serviceIntent = new Intent(activity.getApplicationContext(),
				ServiceMulticastHeartbeat.class);
		
		Bundle bundle = new Bundle();
		bundle.putBoolean("isServer", isServer);
		bundle.putBoolean("displayLog", displayHeartbeatLog);
		serviceIntent.putExtras(bundle);
		activity.bindService(serviceIntent, heartbeatServiceConn,
				Context.BIND_AUTO_CREATE);
	}

	/**
	 * 
	 * @return
	 * @throws Exception
	 */
	private byte[] rebuildBlock(PivotDeGauss pdg, String cmd, long blockNumber)
			throws Exception {
		Matrice mXor = new MatriceImpl(BLOCK);
		Format f = new FormatImpl();
		DatagramPacket dp;
		byte[] tmp;
		int cpt = 0;
		do {
			try {

				dp = rData.receivePacket(1000);
				cpt++;

				tmp = dp.getData();

				if (f.respectFormat(tmp, PACKET)
						&& f.getBlockNumber(tmp) == blockNumber) {

					if (f.isXor(tmp)) {
						/*mXor.addEquation(f.format(tmp, BLOCK, PACKET));
						if (mXor.getLength() >= BLOCK || pdg.resoudre(mXor)) {
							return mXor.rebuildFile();
						}*/
					} else {
						byte[] res = new byte[PACKET];
						System.arraycopy(tmp, 1 + File.SIZE_OF_BLOCK_NUMBER,
								res, 0, PACKET);
						return res;
					}
				} else {
					ct.send(cmd);
					if (cpt % 1000 == 0) {
						System.out
								.println("JE SUIS UN ELSE (pas respect format)!!!");
						System.out.println("On veut : " + blockNumber
								+ ", on a : " + f.getBlockNumber(tmp));
					}
				}

			} catch (Exception e) {
				ct.send(cmd);
				LogHelper.information(logTag, "Exception in rebuild blocks ");
			}
		} while (true);
	}

	private void waitForDevices() {
		boolean exception = true;
		long timestamp = System.nanoTime();
		while (exception) {
			try {
				LogHelper.information(logTag, "init available devices : "
						+ availableDevices.keySet().size());
				if (availableDevices.keySet().size() == 0) {
					Thread.sleep(2000);
					continue;
				}
				publishProgress("Il a fallu : "
						+ new DecimalFormat("##.##")
								.format((System.nanoTime() - timestamp)
										* Math.pow(10, -9)) + " s");
				exception = false;
			} catch (Exception e) {
			}
		}
	}

	/**
	 * 
	 * @param start
	 * @param blockNumber
	 */
	private void checkCommandClient(String command) {
		DatagramPacket dp;
		String ip, msg;
		List<String> list = new ArrayList<String>();
		try {
			while (list.isEmpty()
					|| !list.containsAll(availableDevices.keySet())) {
				/** **/
				dp = receiver.receivePacket();
				ip = dp.getAddress().getHostAddress();
				msg = new String(dp.getData()).trim();

				/** **/
				if (msg.equalsIgnoreCase(command) && !list.contains(ip)) {
					list.add(ip);
				}

				/** remove disconnected client **/
				for (String str : list) {
					if (!availableDevices.containsKey(str)) {
						list.remove(str);
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			LogHelper.erreur(logTag, "exception in the while");
		}
	}

	/**
	 * 
	 * @param command
	 */
	private void initTranfert(String command, String response) {
		DatagramPacket dp;
		String ip, msg;
		List<String> list = new ArrayList<String>();

		while (list.isEmpty() || !list.containsAll(availableDevices.keySet())) {
			/** **/
			try {
				sender.send(command);
				dp = receiver.receivePacket(2000);
				ip = dp.getAddress().getHostAddress();
				msg = new String(dp.getData()).trim();

				/** **/
				if (msg.equalsIgnoreCase(response) && !list.contains(ip)) {
					list.add(ip);
				}

				/** remove disconnected client **/
				for (String str : list) {
					if (!availableDevices.containsKey(str)) {
						list.remove(str);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private boolean initClient() {
		String ipServer = activity.getString(R.string.ip_server);
		String ipClient = activity.getString(R.string.ip_client);
		String ipData = activity.getString(R.string.ip_data);
		publishProgress("initClient()");

		try {
			sender = new SenderMulticast(InetAddress.getByName(ipServer),
					Integer.parseInt(activity.getString(R.string.port_server)));
			receiver = new ReceiverMulticast(InetAddress.getByName(ipClient),
					Integer.parseInt(activity.getString(R.string.port_client)));

			rData = new ReceiverMulticast(InetAddress.getByName(ipData),
					Integer.parseInt(activity.getString(R.string.port_data)));

			/** syncronisation file tranfert **/
			ct = new CommandThread(receiver, sender, FILE_LENGTH, LENGTH_OK,
					EOF);
			ct.start();

			// do {
			publishProgress("\t trying receive");
			/* isOkInit = */
			do {
				Thread.sleep(1000);
			} while (!ct.getIsInit());

			fileSize = ct.getFileSize();

			// } while (!isOkInit);

			return true;
			/** flux data transfert **/

		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}

	}

	private boolean initServer(java.io.File file) {
		// String msg = "";
		String ipServer = activity.getString(R.string.ip_server);
		String ipClient = activity.getString(R.string.ip_client);
		String ipData = activity.getString(R.string.ip_data);

		publishProgress("initServer()");

		try {
			sender = new SenderMulticast(InetAddress.getByName(ipClient),
					Integer.parseInt(activity.getString(R.string.port_client)));
			receiver = new ReceiverMulticast(InetAddress.getByName(ipServer),
					Integer.parseInt(activity.getString(R.string.port_server)));
			sData = new SenderMulticast(InetAddress.getByName(ipData),
					Integer.parseInt(activity.getString(R.string.port_data)));

		} catch (Exception e) {
			e.printStackTrace();
			publishProgress("initialization failed");
			return false;
		}

		// boolean isOkInit = false;
		// do {
		/*
		 * try { sender.send(FILE_LENGTH + file.length()); } catch (Exception e)
		 * { e.printStackTrace(); }
		 * 
		 * try { publishProgress("receive()"); msg =
		 * receiver.receive(Integer.parseInt
		 * (activity.getString(R.string.timeout_multicast))); } catch (Exception
		 * e) { continue; }
		 * 
		 * if (msg.contains(LENGTH_OK)) isOkInit = true;
		 */

		initTranfert(FILE_LENGTH + file.length(), LENGTH_OK);

		// } while (!isOkInit);

		LogHelper.information("Protocole", "FIN");
		return true;
	}

	@Override
	protected void onPostExecute(String result) {
		LogHelper.entree(logTag, "onPostExecute");
		publishProgress("Transfert finished.");
		activity.unbindService(heartbeatServiceConn);
	}

	@Override
	protected void onProgressUpdate(String... values) {
		// LogHelper.entree(logTag, "onProgressUpdate");
		
		if (values.length == 1) {
			if (values[0].equals("NOTIFY_DATA_CHANGED")) {
				listAdapter.notifyDataSetChanged();
				return;
			}
			if (activity.textViewScroll.length() > 5000) {
				activity.textViewScroll.setText(values[0]);
				return;
			}
			
			activity.textViewScroll.append("\n" + values[0]);
			activity.scrollView.fullScroll(View.FOCUS_DOWN);
		} else if (values.length == 2) {
			if(values[0].equals("UPDATE_PATH")){
				//activity.vv.setVideoPath(values[1]);
			}
			else if (values[0].equals("SUFFICIENT_VIDEO")){
				activity.startVideoPlayer(values[1]);
				return;
			}
			listAdapter.appendToHeartbeatLog(values[0]);
		}
	}

	public void doProgress(String... values) {
		publishProgress(values);
	}
	
	public void onDismiss() {
		this.cancel(true);
	}

	/**
	 * Call back du bind avec le service
	 * 
	 * L'envoi du message du service est fait dans le call back
	 */
	private ServiceConnection heartbeatServiceConn = new ServiceConnection() {

		protected Messenger serviceMessenger;

		public void onServiceConnected(ComponentName className,
				IBinder serviceBinder) {
			LogHelper.entree(logTag, "onServiceConnected");

			// Recuperation du messenger
			serviceMessenger = new Messenger(serviceBinder);
			flagBind = true;

			// preparation du message
			Message msg = Message.obtain(null,
					ExchangeProperties.MESSAGE_HELLO, 0, 0);
			Bundle bundle = msg.getData();
			bundle.putBoolean("isServer", isServer);
			// enregistrement du handler de l'activite
			msg.replyTo = messenger;

			try {
				serviceMessenger.send(msg);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}

		public void onServiceDisconnected(ComponentName arg0) {
			LogHelper.entree(logTag, "onServiceDisconnected");

			flagBind = false;

		}
	};

	/**
	 * Handler des messages entrant
	 * 
	 */
	class IncomingHandler extends Handler {

		TransferTask task;

		IncomingHandler(TransferTask receivingTask) {
			LogHelper.entree(logTag, "IncomingHandler");
			this.task = receivingTask;
		}

		@Override
		public void handleMessage(Message msg) {
			// LogHelper.entree(logTag, "handleMessage");
			Bundle bundle = null;

			switch (msg.what) {
			case ExchangeProperties.MESSAGE_HELLO:
				// LogHelper.information(logTag, "RESPONSE HELLO received");
				break;
			case ExchangeProperties.MESSAGE_NUMBER_DEVICES:
				// Methode permettant de recuperer le nombre de devices pour
				// election
				// LogHelper.information(logTag,
				// "MESSAGE NUMBER DEVICES received");
				bundle = msg.getData();
				// On recupere le nombre de ponger
				numberInterlocutor = bundle.getInt("numberPonger");
				LogHelper.information(logTag, "numberPonger = "
						+ numberInterlocutor);
				break;
			case ExchangeProperties.MESSAGE_AVAILABLE_DEVICES:
				// Message permettant de mettre Ã jour la liste des devices
				// accessibles
				// LogHelper.information(logTag, "MESSAGE AVAILABLE DEVICES");
				bundle = msg.getData();
				availableDevices = (HashMap<String, Boolean>) bundle
						.getSerializable("availableDevices");
				listAdapter.setAvailableDevicesKeys(availableDevices);

				break;
			case ExchangeProperties.MESSAGE_UPDATE_UI:
				bundle = msg.getData();
				doProgress(bundle.getString("message"), "heartbeat log");
				break;
			default:
				super.handleMessage(msg);
			}
		}
	}

}